/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** 
*   Copyright 2014 Jan-Marwin Wollschläger                                   *
*                                                                            *
*   Licensed under the Apache License, Version 2.0 (the "License");          *
*   you may not use this file except in compliance with the License.         *
*   You may obtain a copy of the License at                                  *
*                                                                            *
*       http://www.apache.org/licenses/LICENSE-2.0                           *
*                                                                            *
*   Unless required by applicable law or agreed to in writing, software      *
*   distributed under the License is distributed on an "AS IS" BASIS,        *
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
*   See the License for the specific language governing permissions and      *
*   limitations under the License.                                           *
** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **   */   

import java.util.ArrayList;

/**
*  A simple implementation of unit tests.
*  @author Jan-Marwin Wollschläger
**/
class Unit 
{
   /* Make static references to all instantiated unit tests. */
   private static ArrayList<Unit> allUnits = new ArrayList<Unit>();
  
   /* Data member for holding track of the test cases */
   private ITestCase testCase;
   
   /* Was this test successful? */
   private boolean success;
   
   /** Unit can only be instantiated with a corresponding test case 
   * @param ITestCase testCase - The test case corresponding to this 
   * Unit instance. After calling the static method runAllUnitTests
   * of this class, all test cases of the listed Unit instances
   * will be run (All ITestCase instances must implement 
   * the method runTestCase).
   **/
   public Unit(ITestCase testCase)
   {
     this.testCase  = testCase;  // So add the test case as data member
     allUnits.add(this);         // And also add this Unit instance to 
                                 // the static list of all Unit instances
     success = true;             // First assume successful test and set
                                 // it to false if an error occured!
   }

   /**
   * @param obj1, obj2 - The two objects that should be equal, where equal
   * means that the method obj1.equals(obj2) returns true. Otherwise the
   * test fails giving information about what and where the 
   * (assertion) error occured during testing.
   **/  
   public void _equals(Object obj1, Object obj2)
   {
     if(!obj1.equals(obj2))
     {// Objects arent equal, so print an error message!
        printError("Object " + obj1 + " doesn't equal object " + obj2);
     }// Objects are equal, so skip
   }
   
   /**
   * Same as _equals(obj1,obj2), just a convenience.
   **/
   public void _assertEquals(Object obj1, Object obj2)
   {// Delegate call to _equals ...
       _equals(obj1, obj2);
   }
   
   /**
   * @param boolean b - The boolean that should be true. If
   * on the other hand b is false, the test fails.
   **/
   public void _equalsTrue(boolean b)
   {
       if(!b)
       {
         printError("Boolean "+b+" is not True.");
       }
   }
   
   /**
   * Same as _equalsTrue(b).
   **/
   public void _assertTrue(boolean b){
      _equalsTrue(b);
   }
   
   /**
   * Works similar to _equalsTrue(boolean b), but instead
   * gives an error message if b is true.
   * @see _equalsTrue(boolean b)
   **/
   public void _equalsFalse(boolean b)
   {
      if(b)
      {
        printError("Boolean "+b+"is not True.");
      } 
   }
   
   public void _assertFalse(boolean b)
   {
       _equalsFalse(b); 
   }
   
   public void _assertNull(Object obj)
   {
      if(obj != null)
      {
        printError("Object "+obj+"is not Null.");
      } 
   }
   
   public void _assertNotNull(Object obj)
   {
      if(obj == null)
      {
        printError("Object "+obj+"is Null.");
      }
   }
   
   public void _assertSame(Object obj1, Object obj2)
   {
      if(obj1 != obj2)
      {
        printError("Object "+obj1+" isn't the same object as "+obj2);
      } 
   } 
   
   public void _assertArrayEquals(Object[] objs1, Object[] objs2)
   {
     if(!isArrayEqual(objs1, objs2))
     {
       printError("Array of objects "+objs1+
       " isn't the same array of object as "+objs2);
     }
   }
   
   public void _assertNotArrayEquals(Object[] objs1, Object[] objs2)
   {
     if(isArrayEqual(objs1, objs2))
     {
       printError("Array of objects "+objs1+
       " is the same array of object as "+objs2+
       " but should be different");
     }
   }
   
  
   
   
   private boolean isArrayEqual(Object[] objs1, Object[] objs2)
   {
      for(int q=0; q<objs1.length; q++)
      {
         if(!objs1[q].equals(objs2[q]))
           return false;
      } 
      return true;
   }
   
   
   /**
   * Method for printing out useful information about (assertion) errors
   * that occuring during testing. Taking a message msg, this method
   * prints out the StackTrace showing exactly where the error occured.
   **/
   private void printError(String msg)
   {
    /* Get the name of the current class. TODO: work from here! */ 
    StackTraceElement[] elts = new Throwable().getStackTrace(); 
    for(StackTraceElement elt : elts)
       println(errorMessageForThrowable(elt));
    println("This is because:");
    println(msg);
    success = false;
   }
   
   private String errorMessageForThrowable(StackTraceElement elt)
   {
      return "Error occured in file " + elt.getFileName() + " in class " + 
      elt.getClassName() + " in the method " + elt.getMethodName() +
      " in line " + elt.getLineNumber();
   }
   
   public static void println(String str)
   {
      System.out.println(str); 
   }
   
   /**
   * Run all instantiated TestCases. e.g. if you have instantiated the 
   * test cases testCase1, testCase2, testCase3 and in the test cases
   * instantiated the corresponding Unit instances unit1= new Unit(testCase1),
   * unit2= new Unit(testCase2) and unit3= new Unit(testCase3), then the 
   * methods testCase1.runTestCase(), testCase2.runTestCase() and 
   * testCase3.runTestCase() will be called thus performing your unit test
   * code.
   **/
   public static void runAllUnitTests()
   {
      for(Unit u : allUnits)
      {
        u.testCase.runTestCase();
      } 
      int successCount = 0;
      int overallCount = 0;
      for(Unit u : allUnits)
      {
         overallCount++;
         if(u.success)
           successCount++;
      }
      if(overallCount-successCount!=0)
        println("There are "+(overallCount-successCount)+" tests "+
        "exiting with an error.");
      else
        println("There are no tests "+
        "exiting with an error.");
      println("From "+overallCount+" unit tests "+successCount+
      " unit tests are running without errors.");
      
   }
  
}
